package com.zwcl.common.core.redis;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 基于redis实现分布式锁
 *
 * @author tanlj
 * @email Alex.tan@wetax.com.cn
 * @date 2019/7/24
 */
@Slf4j
//@Component
public class RedisLock {

    //默认锁过期毫秒
    private static final long DEFAULT_EXPIRE = 5000L;
    //默认锁等待超时毫秒
    private static final long DEFAULT_TIMEOUT = -1L;
    //锁值
    private static final String LOCK = "lock";

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 尝试获取锁资源（没有获得不等待立刻返回失败）
     *
     * @param lockName 锁key
     * @return
     */
    public boolean tryLock(String lockName) {
        return tryLock(lockName, DEFAULT_EXPIRE);
    }

    /**
     * 尝试获取锁资源（没有获得不等待立刻返回失败）
     *
     * @param lockName 锁key
     * @param expire   锁过期时间 ms
     * @return
     */
    public boolean tryLock(String lockName, long expire) {
        return lock(lockName, expire, DEFAULT_TIMEOUT);
    }

    /**
     * @param lockName 锁key
     * @param expire   锁过期时间 ms
     * @param timeOut  锁超时时间（超过此时间获取不到锁自动失败） ms
     * @return
     */
    public boolean lock(String lockName, long expire, long timeOut) {
        return lock(lockName, expire, timeOut, 1000L);
    }

    /**
     * @param lockName     锁key
     * @param expire       过期时间 ms
     * @param timeOut      锁超时时间（超过此时间获取不到锁自动失败） ms
     * @param waitInterval 轮询睡眠时间 ms
     * @return
     */
    public boolean lock(String lockName, long expire, long timeOut, long waitInterval) {
        if (StringUtils.isEmpty(lockName)) {
            log.error("锁名不能空");
            return false;
        }
        if (expire < 1) {
            log.error("时间设置有误");
            return false;
        }
        try {
            while (true) {
                long timeOutAt = timeOut + System.currentTimeMillis();
                boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(lockName, LOCK);
                if (isLock) {
                    // 设置成功
                    stringRedisTemplate.expire(lockName, expire, TimeUnit.MILLISECONDS);
                    return true;
                }
                long ttl = stringRedisTemplate.getExpire(lockName); // 单位: 秒
                if (ttl < 0) {
                    // 上一获取锁的调用者调用setnx后，没能setexpiretime（crash导致）
                    // 可继续使用锁
                    stringRedisTemplate.expire(lockName, expire, TimeUnit.MILLISECONDS);
                    return true;
                }
                if (timeOut <= 0 || System.currentTimeMillis() > timeOutAt) {
                    break;
                }
                Thread.sleep(waitInterval);
            }
        } catch (Exception e) {
            log.error(e.getCause().getMessage());
        }
        return false;
    }

    /**
     * 解锁
     *
     * @param lockName 锁key
     * @return
     */
    public boolean unlock(String lockName) {
        try {
            stringRedisTemplate.delete(lockName);
        } catch (Exception e) {
            log.error(e.getCause().getMessage());
        }
        return false;
    }
}
